package com.rp.common.config.shiro;

import org.apache.commons.lang.StringUtils;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.Order;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author: wangsaichao
 * @date: 2018/5/10
 * @description: Shiro配置
 */
@Order(96)
@Configuration
public class ShiroConfig {

//    @Autowired
//    private SysUserService sysUserService;
//
//    @Autowired
//    private SysLogService sysLogService;


    @Value("${spring.redis.host}")
    private String HOST;

    @Value("${spring.redis.port}")
    private Integer PORT;

    @Value("${spring.redis.password}")
    private String PASSWORD;

    @Value("${spring.redis.timeout}")
    private Integer TIMOUT;

    /**
     * 加密的次数
     */
    final public static Integer HASH_ITERATIONS = 2;

    /**
     * rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
     */
    final public static String REMEMBER_ME_COOKIE_SALT = "1AvVhmFLUs0KTA3Kprsdag==";

    /**
     * rememberMe cookie 失效时间 单位秒  30天
     */
    final public static Integer REMEMBER_ME_TIMEOUT = 30 * 24 * 60 * 60;

    /**
     * shiro cache 失效时间 单位秒  1天
     */
    final public static Integer SHIRO_CACHE_TIMEOUT = 1 * 24 * 60 * 60;

    //session在redis中的保存时间,最好大于session会话超时时间 秒
    final public static Integer session_redis = 3 * 24 * 60 * 60;


    final public static String ANON = "anon";
    final public static String KICKOUT = "kickout";
    final public static String ANON_KICKOUT = "anon,kickout";

    final public static String URLFILTER = "urlFilter";
    final public static String ANON_URLFILTER = "anon,urlFilter";

    final public static String ANON_KICKOUT_URLFILTER = "anon,kickout,urlFilter";

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意：初始化ShiroFilterFactoryBean的时候需要注入：SecurityManager
     * Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截
     *
     * @param securityManager
     * @return ShiroFilterFactoryBean
     */
    @Bean(name = "shirFilter")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //必须设置 SecurityManager,Shiro的核心安全接口
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //这里的/login是后台的接口名,非页面，如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/redirect/loginIN");
        //这里的/index是后台的接口名,非页面,登录成功后要跳转的链接
        //shiroFilterFactoryBean.setSuccessUrl("/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        //自定义拦截器限制并发人数,参考博客
        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        //限制同一帐号同时在线的个数

        filtersMap.put("kickout", kickoutSessionControlFilter());
        //配置自定义登出 覆盖 logout 之前默认的LogoutFilter
        filtersMap.put("urlFilter", urlFilter());
        //统计登录人数
        shiroFilterFactoryBean.setFilters(filtersMap);


        // 配置访问权限 必须是LinkedHashMap，因为它必须保证有序
        // 过滤链定义，从上向下顺序执行，一般将 /**放在最为下边 一定要注意顺序,否则就不好使了
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->

        filterChainDefinitionMap.put("/sys-user/login", ANON_KICKOUT_URLFILTER);
        //logout是shiro提供的过滤器
        filterChainDefinitionMap.put("/sys-user/logout", ANON_URLFILTER);
        filterChainDefinitionMap.put("/", ANON);

        //访问受限 ========================================================
        filterChainDefinitionMap.put("/redirect/accessRestricted", ANON);
        filterChainDefinitionMap.put("/sys-utils/unfreeze", ANON);
        //================================================================


        //swagger ========================================================
        filterChainDefinitionMap.put("/swagger-resources/**", ANON);
        filterChainDefinitionMap.put("/webjars/**", ANON);
        filterChainDefinitionMap.put("/v2/**", ANON);
        filterChainDefinitionMap.put("/swagger-ui.html/**", ANON);
        filterChainDefinitionMap.put("/doc.html", ANON);
        //================================================================

        //SQL监控服务 druid======================================================
        filterChainDefinitionMap.put("/css/**", ANON);
        filterChainDefinitionMap.put("/js/**", ANON);
        filterChainDefinitionMap.put("/img/**", ANON);
        filterChainDefinitionMap.put("/static/**", ANON);
        filterChainDefinitionMap.put("/druid/**", ANON);
        //================================================================

        //验证码接口 ========================================================
        filterChainDefinitionMap.put("/sys-user/Captcha.jpg", ANON);
        //================================================================


        //测试开放 =========================================================
//        filterChainDefinitionMap.put("/sys-user/unlockAccount", ANON);
//        filterChainDefinitionMap.put("/sys-user/getMd5PassWord", ANON);

        //================================================================

        //自用开放 =========================================================
//        filterChainDefinitionMap.put("/test/**", ANON);
//        filterChainDefinitionMap.put("/sys-test/**", ANON);

        //================================================================

        //activiti 流程 =========================================================
        filterChainDefinitionMap.put("/activiti/**", ANON);
        filterChainDefinitionMap.put("/editor/stencilset", ANON);
        filterChainDefinitionMap.put("/model/*/save", ANON);
        filterChainDefinitionMap.put("/model/*/json", ANON);
        filterChainDefinitionMap.put("/models/**", ANON);
        filterChainDefinitionMap.put("/act/**", ANON);

        //================================================================

        //外高桥数据接收=========================================================
        filterChainDefinitionMap.put("/api/**", ANON);

        //================================================================

        //外部调用开放 =========================================================
        //filterChainDefinitionMap.put("/lt/**", ANON);
        filterChainDefinitionMap.put("/lt/reception/**", ANON);
        filterChainDefinitionMap.put("/lt/ammeter/getOneEqId", ANON);
        filterChainDefinitionMap.put("/lt/ammeter/getAmmeterData", ANON);
        filterChainDefinitionMap.put("/apply/getToken", ANON);

        //兰州的 先开放
        filterChainDefinitionMap.put("/elevator/**", ANON);



        //================================================================

        //ruff=========================================================
        filterChainDefinitionMap.put("/ruff/**", ANON);

        //================================================================

        //主要这行代码必须放在所有权限设置的最后，不然会导致所有 url 都被拦截 剩余的都需要认证
        //其他资源都需要认证  authc 表示需要认证才能进行访问 user表示配置记住我或认证通过可以访问的地址
        filterChainDefinitionMap.put("/**", "user,kickout,urlFilter");

//       shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);


        return shiroFilterFactoryBean;

    }

    /**
     * 配置核心安全事务管理器
     *
     * @return
     */
    @Bean(name = "securityManager")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置自定义realm.
        securityManager.setRealm(shiroRealm());
        //配置记住我 参考博客：
        securityManager.setRememberMeManager(rememberMeManager());

        //配置 ehcache缓存管理器 参考博客：
        securityManager.setCacheManager(cacheManagerShiro());

        //配置自定义session管理，使用ehcache 或redis
        securityManager.setSessionManager(sessionManager());

        return securityManager;
    }

    /**
     * 配置Shiro生命周期处理器
     *
     * @return
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 身份认证realm; (这个需要自己写，账号密码校验；权限等)
     *
     * @return
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCachingEnabled(true);
        //启用身份验证缓存，即缓存AuthenticationInfo信息，默认false
        shiroRealm.setAuthenticationCachingEnabled(true);
        //缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
        shiroRealm.setAuthenticationCacheName("authenticationCache");
        //启用授权缓存，即缓存AuthorizationInfo信息，默认false
        shiroRealm.setAuthorizationCachingEnabled(true);
        //缓存AuthorizationInfo信息的缓存名称  在ehcache-shiro.xml中有对应缓存的配置
        shiroRealm.setAuthorizationCacheName("authorizationCache");
        //配置自定义密码比较器
        shiroRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher());
        return shiroRealm;
    }

    //=======================================================================================================================
    //权限注解模块

    /**
     * *
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * *
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * * @return
     */
//    @Bean
//    @DependsOn({"lifecycleBeanPostProcessor"})
//    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
//        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
//        advisorAutoProxyCreator.setProxyTargetClass(true);
//        return advisorAutoProxyCreator;
//    }
//
//    @Bean
//    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
//        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
//        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
//        return authorizationAttributeSourceAdvisor;
//    }

//=======================================================================================================================


    /**
     * cookie对象;会话Cookie模板 ,默认为: JSESSIONID 问题: 与SERVLET容器名冲突,重新定义为sid或rememberMe，自定义
     *
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie() {
        //这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //setcookie的httponly属性如果设为true的话，会增加对xss防护的安全系数。它有以下特点：
        //setcookie()的第七个参数
        //设为true后，只能通过http访问，javascript无法访问
        //防止xss读取cookie
        simpleCookie.setHttpOnly(false);
        simpleCookie.setPath("/");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(REMEMBER_ME_TIMEOUT);
        return simpleCookie;
    }

    /**
     * cookie管理对象;记住我功能,rememberMe管理器
     *
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode(REMEMBER_ME_COOKIE_SALT));
        return cookieRememberMeManager;
    }

    /**
     * FormAuthenticationFilter 过滤器 过滤记住我
     *
     * @return
     */
    @Bean
    public FormAuthenticationFilter formAuthenticationFilter() {
        FormAuthenticationFilter formAuthenticationFilter = new FormAuthenticationFilter();
        //对应前端的checkbox的name = rememberMe
        formAuthenticationFilter.setRememberMeParam("rememberMe");
        return formAuthenticationFilter;
    }

    /**
     * shiro缓存管理器;
     * 需要添加到securityManager中
     *
     * @return
     */
    @Bean(name = "cacheManagerShiro")
    public RedisCacheManager cacheManagerShiro() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        //redis中针对不同用户缓存
        redisCacheManager.setPrincipalIdFieldName("username");
        //用户权限信息缓存时间
        redisCacheManager.setExpire(SHIRO_CACHE_TIMEOUT);
        return redisCacheManager;
    }


    /**
     * 让某个实例的某个方法的返回值注入为Bean的实例
     * Spring静态注入
     *
     * @return
     */
    @Bean
    public MethodInvokingFactoryBean getMethodInvokingFactoryBean() {
        MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
        factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        factoryBean.setArguments(new Object[]{securityManager()});
        return factoryBean;
    }

    /**
     * 配置session监听
     *
     * @return
     */
    @Bean
    public ShiroSessionListener sessionListener() {
        ShiroSessionListener sessionListener = new ShiroSessionListener();
        return sessionListener;
    }

    /**
     * 配置会话ID生成器
     *
     * @return
     */
    @Bean
    public SessionIdGenerator sessionIdGenerator() {
        return new JavaUuidSessionIdGenerator();
    }


    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(HOST + ":" + PORT);
        System.out.println("PASSWORD" + PASSWORD);
        if (StringUtils.isNotBlank(PASSWORD)) {
            redisManager.setPassword(PASSWORD);
        }
        redisManager.setTimeout(TIMOUT);
        System.out.println(redisManager.toString());
        return redisManager;
    }

    /**
     * SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件
     * MemorySessionDAO 直接在内存中进行会话维护
     * EnterpriseCacheSessionDAO  提供了缓存功能的会话维护，默认情况下使用MapCache实现，内部使用ConcurrentHashMap保存缓存的会话。
     *
     * @return
     */
    @Bean
    public SessionDAO sessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        //session在redis中的保存时间,最好大于session会话超时时间
        redisSessionDAO.setExpire(session_redis);
        return redisSessionDAO;
    }

    /**
     * 配置保存sessionId的cookie
     * 注意：这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie
     *
     * @return
     */
    @Bean("sessionIdCookie")
    public SimpleCookie sessionIdCookie() {
        //这个参数是cookie的名称
        SimpleCookie simpleCookie = new SimpleCookie("zid");
        //setcookie的httponly属性如果设为true的话，会增加对xss防护的安全系数。它有以下特点：
        //setcookie()的第七个参数
        //设为true后，只能通过http访问，javascript无法访问
        //防止xss读取cookie
        simpleCookie.setHttpOnly(false);
        simpleCookie.setPath("/");
        //maxAge=-1表示浏览器关闭时失效此Cookie
        simpleCookie.setMaxAge(-1);
        return simpleCookie;
    }

    /**
     * 配置会话管理器，设定会话超时及保存
     *
     * @return
     */
    @Bean("shiroSessionManager")
    public SessionManager sessionManager() {

        SessionConfig sessionManager = new SessionConfig();

        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        //配置监听
        listeners.add(new ShiroSessionListener());
        sessionManager.setSessionListeners(listeners);
        sessionManager.setSessionIdCookie(sessionIdCookie());
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setCacheManager(cacheManagerShiro());

        //全局会话超时时间（单位毫秒），默认30分钟  暂时设置为10秒钟 用来测试
        //sessionManager.setGlobalSessionTimeout(10000);//30 * 60 * 1000
        //是否开启删除无效的session对象  默认为true
        sessionManager.setDeleteInvalidSessions(true);
        //是否开启定时调度器进行检测过期session 默认为true
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        //设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
        //暂时设置为 5秒 用来测试
        //sessionManager.setSessionValidationInterval(5000);
        //取消url 后面的 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;

    }


    /**
     * 并发登录控制
     *
     * @return
     */
    @Bean
    public KickoutSessionControlFilter kickoutSessionControlFilter() {
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        //用于根据会话ID，获取会话进行踢出操作的；
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        //使用cacheManager获取相应的cache来缓存用户登录的会话；用于保存用户—会话之间的关系的；
        kickoutSessionControlFilter.setCacheManager(cacheManagerShiro());
        //是否踢出后来登录的，默认是false；即后者登录的用户踢出前者登录的用户；
        kickoutSessionControlFilter.setKickoutAfter(false);
        //同一个用户最大的会话数，默认1；比如2的意思是同一个用户允许最多同时两个人登录；
        kickoutSessionControlFilter.setMaxSession(1);
        //被踢出后重定向到的地址；
        kickoutSessionControlFilter.setKickoutUrl("/redirect/loginIN?kickout=1");
        return kickoutSessionControlFilter;

    }

    /**
     * 配置密码比较器
     *
     * @return
     */
    @Bean("credentialsMatcher")
    public RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher() {
        RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(cacheManagerShiro());
        //如果密码加密,可以打开下面配置
        //加密算法的名称
        retryLimitHashedCredentialsMatcher.setHashAlgorithmName("MD5");
        //配置加密的次数
        retryLimitHashedCredentialsMatcher.setHashIterations(HASH_ITERATIONS);
        //是否存储为16进制
        retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return retryLimitHashedCredentialsMatcher;
    }

    /**
     * 配置LogoutFilter
     *
     * @return
     */
//    public ShiroLogoutFilter shiroLogoutFilter(){
////        String username = (String) SecurityUtils.getSubject().getPrincipal();
////        SysUser user = sysUserService.getOne(new QueryWrapper<SysUser>().eq("username", username));
////        sysLogService.save(new SysLog(1,"用户名: " + username + ",退出成功！",user.getUsername(),user.getRealname()));
//
//        ShiroLogoutFilter shiroLogoutFilter = new ShiroLogoutFilter();
//        //清除KickoutSessionControlFilter并发登录控制的redis
//        shiroLogoutFilter.setCacheManager(cacheManager());
//        //配置登出后重定向的地址，等出后配置跳转到登录接口
//        shiroLogoutFilter.setRedirectUrl("/loginIn");
//        return shiroLogoutFilter;
//    }
    public UrlFilter urlFilter() {
        UrlFilter urlFilter = new UrlFilter();
        return urlFilter;
    }

}
